package com.tenthoundsqps.service.impl;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.tenthoundsqps.dao.OrderDao;
import com.tenthoundsqps.entity.Order;
import com.tenthoundsqps.service.ElasticsearchService;
import com.tenthoundsqps.service.OrderService;
import com.tenthoundsqps.service.sync.OrderKafkaSyncService;
import com.tenthoundsqps.service.sync.OrderSyncService;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 订单服务实现类
 * 实现高性能订单查询的核心逻辑
 * 
 * 性能优化策略：
 * 1. 多级缓存：Caffeine(本地缓存) -> Redis(分布式缓存) -> 数据库
 * 2. 限流熔断：使用Sentinel保护数据库不被击垮
 * 3. 批量查询：减少网络IO次数
 * 4. 异步处理：非核心业务异步处理
 */
@Service
public class OrderServiceImpl implements OrderService {
    
    private static final Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
    
    /**
     * 订单数据访问对象
     * 通过MyBatis Plus实现数据库操作
     * 
     * 注意：如果IDE提示无法自动装配OrderDao，请尝试以下解决方案：
     * 1. 清理并重新构建项目
     * 2. 刷新IDE缓存
     * 3. 确保OrderDao接口存在于com.tenthoundsqps.dao包中
     */
    @Autowired
    private OrderDao orderDao;
    
    /**
     * Redisson客户端
     * 用于操作Redis分布式缓存
     */
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * Elasticsearch服务
     * 用于订单的全文搜索功能
     */
    @Autowired
    private ElasticsearchService elasticsearchService;
    
    /**
     * 订单同步服务
     * 用于将订单数据同步到Elasticsearch
     */
    @Autowired
    private OrderSyncService orderSyncService;
    
    /**
     * 订单Kafka同步服务
     * 用于通过Kafka异步同步订单数据到Elasticsearch
     */
    @Autowired
    private OrderKafkaSyncService orderKafkaSyncService;
    
    /**
     * Caffeine本地缓存
     * 作为一级缓存，提供最快的访问速度
     * 最大缓存10,000条记录，10分钟后过期
     */
    private Cache<Long, Order> localCache;
    
    // Redis缓存键前缀
    private static final String REDIS_ORDER_PREFIX = "order:";
    private static final String REDIS_ORDER_LIST_PREFIX = "orders:user:";
    
    /**
     * 初始化方法
     * 在Spring Bean创建完成后初始化本地缓存
     */
    @PostConstruct
    public void init() {
        // 初始化本地缓存 Caffeine
        // 最大缓存10,000条记录，写入10分钟后过期
        localCache = Caffeine.newBuilder()
                .maximumSize(10_000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build();
    }
    
    /**
     * 创建订单
     * 演示如何在订单创建时同步数据到Elasticsearch
     * 
     * @param order 订单对象
     * @return 创建的订单
     */
    public Order createOrder(Order order) {
        // 1. 保存订单到数据库
        orderDao.insert(order);
        
        // 2. 同步到Elasticsearch（应用层同步）
        orderSyncService.syncOrderToElasticsearch(order);
        
        // 3. 发送消息到Kafka（异步同步）
        orderKafkaSyncService.sendOrderSyncMessage(order);
        
        logger.info("Order created and synced, orderId: {}", order.getId());
        return order;
    }
    
    /**
     * 更新订单
     * 演示如何在订单更新时同步数据到Elasticsearch
     * 
     * @param order 订单对象
     * @return 更新后的订单
     */
    public Order updateOrder(Order order) {
        // 1. 更新订单到数据库
        orderDao.updateById(order);
        
        // 2. 同步到Elasticsearch（应用层同步）
        orderSyncService.syncOrderToElasticsearch(order);
        
        // 3. 发送消息到Kafka（异步同步）
        orderKafkaSyncService.sendOrderSyncMessage(order);
        
        logger.info("Order updated and synced, orderId: {}", order.getId());
        return order;
    }
    
    /**
     * 删除订单
     * 演示如何在订单删除时同步数据到Elasticsearch
     * 
     * @param orderId 订单ID
     */
    public void deleteOrder(Long orderId) {
        // 1. 从数据库删除订单
        orderDao.deleteById(orderId);
        
        // 2. 从Elasticsearch中删除（应用层同步）
        orderSyncService.deleteOrderFromElasticsearch(orderId);
        
        // 3. 发送消息到Kafka（异步同步）
        orderKafkaSyncService.sendOrderDeleteMessage(orderId);
        
        logger.info("Order deleted and synced, orderId: {}", orderId);
    }
    
    /**
     * 根据订单ID查询订单详情
     * 多级缓存查询策略：Caffeine -> Redis -> 数据库
     * 使用Sentinel进行限流保护，数据库慢查询时自动降级
     * 
     * @param id 订单ID
     * @return 订单信息
     */
    @Override
    @SentinelResource(value = "getOrderById", fallback = "getOrderByIdFallback")
    public Order getOrderById(Long id) {
        // 1. 先查本地缓存(Caffeine)
        // 提供最快的访问速度，适用于热点数据
        Order order = localCache.getIfPresent(id);
        if (order != null) {
            logger.debug("Order found in local cache, id: {}", id);
            return order;
        }
        
        // 2. 查分布式缓存(Redis)
        // 本地缓存未命中时查询Redis
        RMap<String, Object> redisMap = redissonClient.getMap(REDIS_ORDER_PREFIX + id);
        if (!redisMap.isEmpty()) {
            // Redis中存在该订单数据
            order = new Order();
            order.setId(id);
            order.setOrderNo((String) redisMap.get("orderNo"));
            order.setUserId((Long) redisMap.get("userId"));
            order.setMerchantId((Long) redisMap.get("merchantId"));
            order.setMerchantName((String) redisMap.get("merchantName"));
            order.setPhoneNumber((String) redisMap.get("phoneNumber"));
            order.setAmount((java.math.BigDecimal) redisMap.get("amount"));
            order.setStatus((Integer) redisMap.get("status"));
            // 从Redis中读取归档状态
            order.setArchived((Integer) redisMap.get("archived"));
            
            // 放入本地缓存，下次查询可以直接命中
            localCache.put(id, order);
            logger.debug("Order found in Redis cache, id: {}", id);
            return order;
        }
        
        // 3. 查数据库
        // 缓存都未命中时查询数据库
        order = orderDao.selectById(id);
        if (order != null) {
            // 数据库查询到数据后，存入Redis缓存
            RMap<String, Object> map = redissonClient.getMap(REDIS_ORDER_PREFIX + id);
            map.put("orderNo", order.getOrderNo());
            map.put("userId", order.getUserId());
            map.put("merchantId", order.getMerchantId());
            map.put("merchantName", order.getMerchantName());
            map.put("phoneNumber", order.getPhoneNumber());
            map.put("amount", order.getAmount());
            map.put("status", order.getStatus());
            // 存储归档状态
            map.put("archived", order.getArchived());
            // 设置30分钟过期时间
            map.expire(30, TimeUnit.MINUTES);
            
            // 同时存入本地缓存
            localCache.put(id, order);
            logger.debug("Order found in database, id: {}", id);
        }
        
        return order;
    }
    
    /**
     * Sentinel fallback 方法
     * 当系统出现异常或触发限流时的降级处理方法
     * 只查询缓存，不查询数据库，保证系统基本可用
     * 
     * @param id 订单ID
     * @return 订单信息
     */
    public Order getOrderByIdFallback(Long id) {
        logger.warn("Fallback triggered for getOrderById, id: {}", id);
        // 降级处理，只查缓存，不查数据库
        Order order = localCache.getIfPresent(id);
        if (order != null) {
            return order;
        }
        
        RMap<String, Object> redisMap = redissonClient.getMap(REDIS_ORDER_PREFIX + id);
        if (!redisMap.isEmpty()) {
            order = new Order();
            order.setId(id);
            order.setOrderNo((String) redisMap.get("orderNo"));
            order.setUserId((Long) redisMap.get("userId"));
            order.setMerchantId((Long) redisMap.get("merchantId"));
            order.setMerchantName((String) redisMap.get("merchantName"));
            order.setPhoneNumber((String) redisMap.get("phoneNumber"));
            order.setAmount((java.math.BigDecimal) redisMap.get("amount"));
            order.setStatus((Integer) redisMap.get("status"));
            // 从Redis中读取归档状态
            order.setArchived((Integer) redisMap.get("archived"));
            return order;
        }
        
        return null;
    }
    
    /**
     * 根据订单号查询订单详情
     * 
     * @param orderNo 订单编号
     * @return 订单信息
     */
    @Override
    public Order getOrderByNo(String orderNo) {
        QueryWrapper<Order> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        // 只查询未归档的订单（热数据）
        queryWrapper.eq("is_archived", 0);
        return orderDao.selectOne(queryWrapper);
    }
    
    /**
     * 根据用户ID查询订单列表
     * 使用多级缓存策略优化查询性能
     * 
     * @param userId 用户ID
     * @param limit 限制返回订单数量
     * @return 订单列表
     */
    @Override
    @SentinelResource(value = "getOrdersByUserId", fallback = "getOrdersByUserIdFallback")
    public List<Order> getOrdersByUserId(Long userId, int limit) {
        // 1. 先查Redis缓存
        // Redis中存储的是订单ID列表，避免存储大量重复数据
        RMap<String, Object> redisMap = redissonClient.getMap(REDIS_ORDER_LIST_PREFIX + userId);
        if (redisMap.containsKey("orders")) {
            // 缓存中存在订单列表
            List<Long> orderIds = (List<Long>) redisMap.get("orders");
            List<Order> orders = new ArrayList<>();
            for (Long orderId : orderIds) {
                // 复用单个订单查询逻辑，充分利用多级缓存
                Order order = getOrderById(orderId);
                if (order != null) {
                    orders.add(order);
                }
            }
            logger.debug("Orders found in Redis cache, userId: {}", userId);
            return orders;
        }
        
        // 2. 查数据库
        // 缓存未命中时查询数据库
        List<Order> orders = orderDao.selectByUserId(userId, limit);
        if (!CollectionUtils.isEmpty(orders)) {
            // 数据库查询到数据后，存入Redis缓存
            List<Long> orderIds = orders.stream().map(Order::getId).collect(Collectors.toList());
            // 存储订单ID列表
            redisMap.put("orders", orderIds);
            // 设置10分钟过期时间
            redisMap.expire(10, TimeUnit.MINUTES);
            
            // 将每个订单详情也存入缓存
            for (Order order : orders) {
                RMap<String, Object> map = redissonClient.getMap(REDIS_ORDER_PREFIX + order.getId());
                map.put("orderNo", order.getOrderNo());
                map.put("userId", order.getUserId());
                map.put("merchantId", order.getMerchantId());
                map.put("merchantName", order.getMerchantName());
                map.put("phoneNumber", order.getPhoneNumber());
                map.put("amount", order.getAmount());
                map.put("status", order.getStatus());
                // 存储归档状态
                map.put("archived", order.getArchived());
                // 设置30分钟过期时间
                map.expire(30, TimeUnit.MINUTES);
                
                // 同时存入本地缓存
                localCache.put(order.getId(), order);
            }
            logger.debug("Orders found in database, userId: {}", userId);
        }
        
        return orders;
    }
    
    /**
     * Sentinel fallback 方法
     * 用户订单列表查询的降级处理方法
     * 
     * @param userId 用户ID
     * @param limit 限制返回订单数量
     * @return 订单列表
     */
    public List<Order> getOrdersByUserIdFallback(Long userId, int limit) {
        logger.warn("Fallback triggered for getOrdersByUserId, userId: {}", userId);
        // 降级处理，只查缓存，不查数据库
        RMap<String, Object> redisMap = redissonClient.getMap(REDIS_ORDER_LIST_PREFIX + userId);
        if (redisMap.containsKey("orders")) {
            List<Long> orderIds = (List<Long>) redisMap.get("orders");
            List<Order> orders = new ArrayList<>();
            for (Long orderId : orderIds) {
                // 使用降级方法获取订单详情
                Order order = getOrderByIdFallback(orderId);
                if (order != null) {
                    orders.add(order);
                }
            }
            return orders;
        }
        return new ArrayList<>();
    }
    
    /**
     * 根据手机号查询订单（使用ES）
     * 实际项目中应该先调用ES获取订单ID列表，再批量查询订单详情
     * 
     * @param phone 手机号
     * @param limit 限制返回订单数量
     * @return 订单列表
     */
    @Override
    public List<Order> searchOrdersByPhone(String phone, int limit) {
        // 调用ES服务查询订单ID列表
        List<Long> orderIds = elasticsearchService.searchOrdersByPhone(phone, limit);
        
        // 根据订单ID列表批量查询订单详情
        List<Order> orders = new ArrayList<>();
        for (Long orderId : orderIds) {
            Order order = getOrderById(orderId);
            if (order != null) {
                orders.add(order);
            }
        }
        
        return orders;
    }
    
    /**
     * 根据商户名称查询订单（使用ES）
     * 实际项目中应该先调用ES获取订单ID列表，再批量查询订单详情
     * 
     * @param merchantName 商户名称
     * @param limit 限制返回订单数量
     * @return 订单列表
     */
    @Override
    public List<Order> searchOrdersByMerchantName(String merchantName, int limit) {
        // 调用ES服务查询订单ID列表
        List<Long> orderIds = elasticsearchService.searchOrdersByMerchantName(merchantName, limit);
        
        // 根据订单ID列表批量查询订单详情
        List<Order> orders = new ArrayList<>();
        for (Long orderId : orderIds) {
            Order order = getOrderById(orderId);
            if (order != null) {
                orders.add(order);
            }
        }
        
        return orders;
    }
    
    /**
     * 获取归档订单（冷数据查询）
     * 
     * @param userId 用户ID
     * @param limit 限制返回订单数量
     * @return 归档订单列表
     */
    @Override
    public List<Order> getArchivedOrdersByUserId(Long userId, int limit) {
        return orderDao.selectArchivedByUserId(userId, limit);
    }
}